﻿using Common.Cache;
using Common.Encrypt;
using Common.Logger;
using Common.Web.Attributes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Common.Web
{
    /// <summary>
    /// 接口响应缓存过滤器
    /// </summary>
    public class ResponseCacheFilter : ActionFilterAttribute
    {
        private readonly static ICache MemoryCache = new MemoryCache();
        /// <summary>
        /// 执行action之前
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            //仅允许GET请求缓存，非GET请求，直接返回
            if (!CanCache(context.HttpContext))
            {
                return;
            }
            var cached = context.ActionDescriptor.EndpointMetadata.OfType<CachedAttribute>()?.FirstOrDefault();
            if (cached == null)//未标记为需要缓存，直接返回
            {
                return;
            }
            //使用请求地址作为缓存KEY名
            string key = GetCacheKey(context);
            var res = MemoryCache.Get<CachedResult>(key);
            if (res == null)
            {
                return;
            }
            context.HttpContext.Response.Headers.Add("X-Cached-Since", res.CachedSince.ToString());
            context.HttpContext.Response.Headers.Add("X-Cached-Duration", string.Concat((long)res.Duration.TotalSeconds, "s"));
            context.Result = res;
        }
        /// <summary>
        /// 必须是GET请求，并且请求头未设置不允许缓存[Cache-Control = no-cache]
        /// </summary>
        /// <param name="ctx"></param>
        /// <returns></returns>
        private static bool CanCache(HttpContext ctx)
        {
            return HttpMethods.IsGet(ctx.Request.Method) && ctx.Request.Headers[HeaderNames.CacheControl] != CacheControlHeaderValue.NoCacheString;
        }
        private static string GetCacheKey(FilterContext context)
        {
            var request = context.HttpContext.Request;
            string token = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any() ? null : (string)request.Headers[HeaderNames.Authorization];
            string unified = string.Concat(request.Path, request.QueryString.Value, token);
            string key = MD5Provider.Encode(unified);
            return key;
        }
        /// <inheritdoc/>
        public override void OnResultExecuted(ResultExecutedContext context)
        {
            if (!CanCache(context.HttpContext))
            {
                return;
            }
            var cached = context.ActionDescriptor.EndpointMetadata.OfType<CachedAttribute>()?.FirstOrDefault();
            if (cached == null)//未标记为需要缓存，直接返回
            {
                return;
            }
            //不是已缓存的结果，并且是属于请求成功的结果才写入缓存
            if (!(context.Result is CachedResult) && context.Result is ObjectResult objRes && objRes.Value is FuncResult funcRes && funcRes.Code == FuncResult._SUCCESS_CODE)
            {
                Console.WriteLine("add to cache");
                string key = GetCacheKey(context);
                MemoryCache.Add(key, new CachedResult(objRes, cached.Duration), cached.Duration);
            }
        }
    }
    /// <summary>
    /// 缓存结果
    /// </summary>
    public class CachedResult : ObjectResult
    {
        /// <summary>
        /// 缓存结果
        /// </summary>
        /// <param name="result"></param>
        /// <param name="duration"></param>
        public CachedResult(ObjectResult result, TimeSpan duration) : this(result.Value, DateTime.Now, duration)
        {
            this.ContentTypes = result.ContentTypes;
            this.DeclaredType = result.DeclaredType;
            this.Formatters = result.Formatters;
            this.StatusCode = result.StatusCode;
        }
        /// <summary>
        /// 缓存结果
        /// </summary>
        /// <param name="value"></param>
        /// <param name="cachedSince"></param>
        /// <param name="duration"></param>
        public CachedResult(object value, DateTime cachedSince, TimeSpan duration) : base(value)
        {
            this.CachedSince = cachedSince;
            this.Duration = duration;
        }
        /// <summary>
        /// 缓存加入时间
        /// </summary>
        public DateTime CachedSince { get; set; }
        /// <summary>
        /// 缓存持续时间
        /// </summary>
        public TimeSpan Duration { get; set; }
    }
}
